#include "luster_parser.hh"

#include <inttypes.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>

#include "luster_int.hh"
#include "luster_lexpr.hh"
#include "luster_log.hh"
#include "luster_status.hh"
#include "luster_test.hh"

namespace luster {

static int get(Parser const *p) {
	return (unsigned char)p->text[p->curr];
}

static void next(Parser *p) {
	if (p->text[p->curr] != 0) {
		p->curr += 1;
	}
}

static Bool is_lower(int c) {
	return Bool{'a' <= c && c <= 'z'};
}

static Bool is_upper(int c) {
	return Bool{'A' <= c && c <= 'Z'};
}

static Bool is_digit(int c) {
	return Bool{'0' <= c && c <= '9'};
}

static Bool is_ws(int c) {
	switch (c) {
	case ' ':
	case '\t':
	case '\n':
		return Bool{true};
	default:
		return Bool{false};
	}
}

static bool is_op_char(int c) {
	switch (c) {
	case '+':
	case '-':
	case '*':
	case '/':

	case '=':
	case '<':
	case '>':

	case '!':
	case '%':
	case '^':
	case '&':

	case '|':
	case ':':
	case ',':
	case '~':
	case ';':
		return true;
	default:
		return false;
	}
}

static void skip_ws(Parser *p) {
	int r;
	while (is_ws(r = get(p))) {
		next(p);
	}
}

static Bool is_symbol_char(int r) {
	return is_lower(r) || is_upper(r) || is_digit(r) || r == '_' ||
		r == '.';
}

static Status parse_op(Parser *p, Lexpr *lexpr) {
	uint32_t len = 0;
	char const *start = p->text + p->curr;
	int r;

	while (is_op_char(r = get(p))) {
		len += 1;
		next(p);
	}

	lexpr->tag = LEXPR_OP;
	lexpr->as.string = Slice{start, len};

	LOG(DEBUG) << "parse operator: " << lexpr->as.string;

	return STATUS_OK;
}

static Status parse_symbol(Parser *p, Slice *string) {
	uint32_t len = 0;
	char const *start = p->text + p->curr;
	int r;

	while (is_symbol_char(r = get(p))) {
		len += 1;
		next(p);
	}

	*string = Slice{start, len};

	LOG(DEBUG) << "parse symbol: " << (*string);
	return STATUS_OK;
}

static Status parse_symbol_lexpr(Parser *p, Lexpr *lexpr) {
	parse_symbol(p, &lexpr->as.string);
	lexpr->tag = LEXPR_SYMBOL;
	return STATUS_OK;
}

static Status parse_string(Parser *p, Slice *out) {
	next(p); // "

	uint32_t len = 0;
	char const *start = p->text + p->curr;
	int c;

	while (true) {
		c = get(p);

		if (c == 0) {
			return STATUS_LIST_CLOSE;
		}

		if (c == '"') {
			next(p);
			break;
		}

		len += 1;
		next(p);
	}

	*out = luster::Slice{start, len};

	return STATUS_OK;
}

static Status parse_string_lexpr(Parser *p, Lexpr *lexpr) {
	lexpr->tag = LEXPR_STRING;
	luster_TRY(parse_string(p, &lexpr->as.string));
	return STATUS_OK;
}

static Status parse_keyword(Parser *p, Lexpr *lexpr) {
	next(p); // #

	Status status = parse_symbol_lexpr(p, lexpr);
	if (status != STATUS_OK) {
		return status;
	}

	if (lexpr->as.string == "t") {
		lexpr->tag = LEXPR_BOOL;
		lexpr->as.yes = true;

		logprint(LOG_DEBUG, "parse: #t");
		return STATUS_OK;
	}

	if (lexpr->as.string == "f") {
		lexpr->tag = LEXPR_BOOL;
		lexpr->as.yes = false;

		logprint(LOG_DEBUG, "parse: #f");
		return STATUS_OK;
	}

	lexpr->tag = LEXPR_KEYWORD;

	logprint(LOG_DEBUG, "parse keyword: #t");
	return STATUS_OK;
}

static Status parse_llist(Parser *p, Lexpr *lexpr) {
	logprint(LOG_DEBUG, "parse s-list start");

	next(p); // '('

	Vec<Lexpr> llist = {0};
	Lexpr *tmp;

	while (true) {
		skip_ws(p);
		if (get(p) == ')') {
			next(p);
			break;
		}

		tmp = vec_append_zero(&llist);

		Status status = parse_lexpr(p, tmp);
		if (status == STATUS_EOF) {
			return STATUS_LIST_CLOSE;
		}

		if (status != STATUS_OK) {
			return status;
		}
	}

	lexpr->tag = LEXPR_LIST;
	lexpr->as.exprs = llist;

	logprint(LOG_DEBUG, "parse s-list end");

	return STATUS_OK;
}

static Status parse_s64(Parser *p, int64_t *out) {
	int r = get(p);

	int64_t tmp = 0;

	while (is_digit(r = get(p))) {
		tmp = tmp * 10 + (r - '0');
		next(p);
	}

	*out = tmp;

	LOG(DEBUG) << "parse i64: " << tmp;

	return STATUS_OK;
}

static Status parse_s32(Parser *p, Lexpr *lexpr) {
	int64_t tmp = 0;

	luster_TRY(parse_s64(p, &tmp));
	lexpr->tag = LEXPR_S32;
	lexpr->as.i32 = tmp;

	return STATUS_OK;
}

Status parse_lexpr(Parser *p, Lexpr *lexpr) {
	skip_ws(p);

	int r = get(p);

	if (r == 0) {
		return STATUS_EOF;
	}

	if (is_digit(r)) {
		return parse_s32(p, lexpr);
	}

	if (is_lower(r) || is_upper(r)) {
		return parse_symbol_lexpr(p, lexpr);
	}

	if (r == '#') {
		return parse_keyword(p, lexpr);
	}

	if (r == '(') {
		return parse_llist(p, lexpr);
	}

	if (r == '"') {
		logprint(LOG_DEBUG, "parse string");
		return parse_string_lexpr(p, lexpr);
	}

	if (is_op_char(r)) {
		return parse_op(p, lexpr);
	}

	logprint(LOG_ERROR, "invalid leading char: %d(%c)", r, r);
	p->err_pos = p->curr;
	return STATUS_LEADING_CHAR;
}

//
// Token
//

static Status parse_symbol_token(Parser *p, Token *token) {
	uint32_t len = 0;
	char const *start = p->text + p->curr;
	int r;

	while (is_symbol_char(r = get(p))) {
		len += 1;
		next(p);
	}

	token->tag = TOKEN_SYMBOL;
	token->as.string = (Slice){.data = start, .len = len};

	logprint(LOG_DEBUG, "parse symbol: %.*s", (int)len, start);

	return STATUS_OK;
}

Status parse_token(Parser *p, Token *token) {
	skip_ws(p);

	int r = get(p);

	if (r == 0) {
		return STATUS_EOF;
	}

	if (r == '(') {
		token->tag = TOKEN_OPEN;
		next(p);
		return STATUS_OK;
	}

	if (r == ')') {
		token->tag = TOKEN_CLOSE;
		next(p);
		return STATUS_OK;
	}

	if (r == '{') {
		token->tag = TOKEN_OPEN_D;
		next(p);
		return STATUS_OK;
	}

	if (r == '}') {
		token->tag = TOKEN_CLOSE_D;
		next(p);
		return STATUS_OK;
	}

	if (is_digit(r)) {
		luster_TRY(parse_s64(p, &token->as.s64));
		token->tag = TOKEN_INTEGER;
		return STATUS_OK;
	}

	if (is_lower(r) || is_upper(r)) {
		luster_TRY(parse_symbol(p, &token->as.string));
		token->tag = TOKEN_SYMBOL;
		return STATUS_OK;
	}

	if (r == '"') {
		luster_TRY(parse_string(p, &token->as.string));
		token->tag = TOKEN_STRING;
		return STATUS_OK;
	}

	LOG(ERROR) << "invalid leading char: " << r << '(' << r << ')';
	p->err_pos = p->curr;

	return STATUS_LEADING_CHAR;
}

luster_TEST(parse_token) {
	Parser p = {.text = "luster_TEST(parse_token) {}"};

	Token token;

	LOG_CHECK_EQ(parse_token(&p, &token), STATUS_OK);
	LOG_CHECK_EQ(token.tag, TokenTag::TOKEN_SYMBOL);

	LOG_CHECK_EQ(parse_token(&p, &token), STATUS_OK);
	LOG_CHECK_EQ(token.tag, TokenTag::TOKEN_OPEN);

	LOG_CHECK_EQ(parse_token(&p, &token), STATUS_OK);
	LOG_CHECK_EQ(token.tag, TokenTag::TOKEN_SYMBOL);

	LOG_CHECK_EQ(parse_token(&p, &token), STATUS_OK);
	LOG_CHECK_EQ(token.tag, TokenTag::TOKEN_CLOSE);

	LOG_CHECK_EQ(parse_token(&p, &token), STATUS_OK);
	LOG_CHECK_EQ(token.tag, TokenTag::TOKEN_OPEN_D);

	LOG_CHECK_EQ(parse_token(&p, &token), STATUS_OK);
	LOG_CHECK_EQ(token.tag, TokenTag::TOKEN_CLOSE_D);

	LOG_CHECK_EQ(parse_token(&p, &token), STATUS_EOF);
	LOG_CHECK_EQ(parse_token(&p, &token), STATUS_EOF);
}

} // namespace luster
